package model

import (
	"bufio"
	j_log "config-client-v1/init/j-log"
	"config-client-v1/utils/tools"
	"context"
	"encoding/json"
	"fmt"
	"github.com/olekukonko/tablewriter"
	"io"
	"net"
	"os"
	"runtime/debug"
	"strings"
	"sync"
	"time"
)

const BYTE_LENGTH = 8192

func (ppe *PPE) Init() {
	if ppe.TCPOnlineMap == nil {
		ppe.TCPOnlineMap = &TCPOnlineMapStu{}
		ppe.TCPOnlineMap.M = make(map[string]*TCPClient)
		ppe.TCPOnlineMap.Register = make(chan *TCPClient, 1000)
		ppe.TCPOnlineMap.Unregister = make(chan *TCPClient, 1000)
	}
}

func (ppe *PPE) RunTCPServer() {
	defer func() {
		if err2 := recover(); err2 != nil {
			j_log.LogError([]interface{}{"RunTCPServer panic", err2})
			fmt.Println(string(debug.Stack()))
		}

		if ppe.RunningState == 3 {
			j_log.LogInfo([]interface{}{"【已停止】", ppe.ProtocolName, ppe.Name})
			ppe.RunningState = 4
			ppe.UnRegister()
			_ = ppe.UpdateRunningState(4, "设备已停止")
			return
		}

		ppe.RunningState = 5
		j_log.LogInfo([]interface{}{"3秒后重连...", ppe.Name})
		ppe.Detail = "正在尝试重连..."
		_ = ppe.UpdateRunningState(5, "正在尝试重连...")
		time.Sleep(3 * time.Second)
		/*--- 这段代码写两次, 这里确保在3秒sleep内的停止操作生效 ---*/
		if ppe.RunningState == 3 {
			j_log.LogInfo([]interface{}{"【已停止】", ppe.ProtocolName, ppe.Name})
			ppe.RunningState = 4
			ppe.UnRegister()
			_ = ppe.UpdateRunningState(4, "设备已停止")
			return
		}
		ppe.RunTCPServer()

	}()
	ppe.Init()
	ppe.Register()

	if ppe.TcpClient == nil {
		ppe.TcpClient = &TCPClient{
			Requests:      NewFSTCPRequestMap(),
			FrameCacheMap: NewFrameCacheMap(),
		}
	}
	config := TCPIPConfig{}
	err0 := json.Unmarshal([]byte(ppe.Config), &config)
	if err0 != nil {
		ppe.RunningState = 3
		ppe.Detail = fmt.Sprintf("tcpClient json解析失败: %v", err0.Error())
		return
	}
	addr := fmt.Sprintf("%s:%d", config.IP, config.Port)
	listener, err := net.Listen("tcp", addr)
	if err != nil {
		j_log.LogError([]interface{}{fmt.Sprintf("【启动失败】%s:%s", ppe.ProtocolName, ppe.Name)})
		return
	}
	defer listener.Close()

	j_log.LogError([]interface{}{fmt.Sprintf("【已启动】%s:%s", ppe.ProtocolName, ppe.Name)})
	ppe.Detail = "已启动"
	_ = ppe.UpdateRunningState(2, "已启动")
	ppe.RunningState = 2 // 启动成功
	ppe.TCPListener = listener

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go ppe.ConnectionPoolHandle(&config, ctx)
	go ppe.TCPTimeoutHandle(&config, ctx)

	// 异步处理发送帧
	for _, frame := range ppe.FrameList {
		if frame.FrameType == "send" && frame.Status == 1 {
			if frame.SendType >= 1 && frame.SendType <= 2 {
				go ppe.tcpServerFrameSendProcess(ctx, frame)
			}
		}
	}

	for {
		conn, err2 := listener.Accept()
		if err2 != nil {
			fmt.Println("listener Accept Error", err2)
			j_log.LogError([]interface{}{fmt.Sprintf("listener Accept Error %s", ppe.Name)})
			return
		}
		remoteAddr := conn.RemoteAddr().String()
		reader := bufio.NewReader(conn)
		client := &TCPClient{
			RemoteAddr:   remoteAddr,
			Connection:   conn,
			Reader:       reader,
			CreateTime:   tools.GetNow(),
			LastDataTime: tools.GetNow(),
		}
		ppe.TCPOnlineMap.Register <- client
		//_ = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
		go ppe.HandleConnection(client)
	}
}

func (ppe *PPE) HandleConnection(client *TCPClient) {
	defer func() {
		if err2 := recover(); err2 != nil {
			j_log.LogError([]interface{}{"HandleConnection panic", err2})
			fmt.Println(string(debug.Stack()))
		}
		ppe.TCPOnlineMap.Unregister <- client
	}()
	var cacheBuffer [ByteLENGTH]byte
	// 同步处理接收帧
	for {
		n, err := client.Reader.Read(cacheBuffer[0:])
		if err != nil {
			if err == io.EOF {
				j_log.LogError([]interface{}{"read error io.EOF.", ppe.Name})
			} else if err == net.ErrClosed || strings.Contains(err.Error(), "use of closed network connection") || strings.Contains(err.Error(), "read: connection reset by peer") || strings.Contains(err.Error(), "closed by the remote host") {
				j_log.LogError([]interface{}{"read error PING.", ppe.Name})
			} else {
				j_log.LogError([]interface{}{"read error unknown.", ppe.Name, err.Error()})
			}
			return
		}
		client.LastDataTime = tools.GetNow()
		buffer := cacheBuffer[0:n]
		str0 := tools.ByteArrayToHexStr(buffer[0:n])
		_ = tools.WriteStringToLog(fmt.Sprintf("[%s~%s]【recv】len=%d#%s", tools.GetNow(), ppe.TcpClient.RemoteAddr, n, str0), "tcp_server_data_all.log", "a") // 可能是粘包异常
		now := tools.GetNow()
		// 输出读到的数据
		time.Sleep(5 * time.Millisecond)
		rawData := tools.ByteArrayToHexStr(buffer[:n])
		//fmt.Printf("【%s】len=%d : %s\n", tools.GetNow(), n, recvData)
		j_log.LogInfo([]interface{}{fmt.Sprintf("【recv】%s, ppe_id=%d len=%d %s", ppe.ProtocolName, ppe.ID, n, rawData)})
		frameMatched := false
		//  :::应该进行两轮 FrameList 循环, 第一轮匹配长度和功能码 , 第二轮再解析变量
	loop1:
		for _, frame := range ppe.FrameList {
			if !(frame.FrameType == "recv" && frame.Status == 1) {
				continue
			}
			if frame.Length != n {
				//j_log.LogError([]interface{}{"长度不匹配, 跳过此帧", frame.Length, n})
				continue loop1
			}
			/*--- 变量解析开始 ---*/
			frameList := make([]*FrameItemParam, 0)
			err = json.Unmarshal([]byte(frame.JsonDetail), &frameList)
			if err != nil {
				j_log.LogError([]interface{}{fmt.Sprintf("%s json解析失败 frame.JsonDetail; %s", ppe.Name, err.Error())})
				return
			}

			data := make([][]string, 0)
			messageList := make([]*QueueValue, 0)
			for _, item := range frameList {
				if item.InputMethod != 1 { // 如果不是变量就校验
					switch item.Type {
					case "包头":
						functionCode := tools.ByteArrayToHexStr(buffer[item.Start : item.End+1])
						if item.RawData != functionCode {
							j_log.LogError([]interface{}{"包头不匹配!", item.RawData, functionCode})
							continue loop1
						}
					case "帧长":
					case "地址":
						functionCode := tools.ByteArrayToHexStr(buffer[item.Start : item.End+1])
						if item.RawData != functionCode {
							j_log.LogError([]interface{}{"地址不匹配!", item.RawData, functionCode})
							continue loop1
						}
					case "功能码":
						functionCode := tools.ByteArrayToHexStr(buffer[item.Start : item.End+1])
						if item.RawData != functionCode {
							j_log.LogError([]interface{}{"功能码不匹配!", item.RawData, functionCode})
							continue loop1
						}
					case "校验":
						if item.CheckMethod == "crc16" {
							checkRes := tools.CheckCRC16Custom(buffer, item.CheckStart, item.CheckEnd, item.Start, item.End)
							if !checkRes {
								j_log.LogError([]interface{}{"CRC16校验失败!", item.CheckStart, item.CheckEnd, item.Start, item.End})
								continue loop1
							} else {
								j_log.LogInfo([]interface{}{"crc16 校验成功"})
							}
						}
					case "帧尾":
						functionCode := tools.ByteArrayToHexStr(buffer[item.Start : item.End+1])
						if item.RawData != functionCode {
							j_log.LogError([]interface{}{"帧尾不匹配!", item.RawData, functionCode})
							continue loop1
						}
					}
				} else { // 是变量则直接解析
					var value float64
					switch item.SpecType {
					case "bool":
						//fmt.Println("单字节:", value, item.MultiByte, item.BoolBits)
						if item.MultiByte == 1 {
							binArr := tools.ByteToBitArray(buffer[item.Start])
							value = float64(binArr[item.BoolBits])
						} else {
							value = float64(buffer[item.Start])
						}
					case "uint8":
						v := buffer[item.Start]
						value = float64(v)
					case "int8":
						v := buffer[item.Start]
						value = float64(int8(v))
					case "uint16":
						rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 2)
						v := tools.BytesToUint16(rawBytes, item.Endian)
						value = float64(v)
					case "int16":
						rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 2)
						v := tools.BytesToUint16(rawBytes, item.Endian)
						value = float64(int16(v))
					case "uint32":
						rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 4)
						v := tools.BytesToUint32(rawBytes, item.Endian)
						value = float64(v)
					case "int32":
						rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 4)
						v := tools.BytesToUint32(rawBytes, item.Endian)
						value = float64(int32(v))
					case "uint64":
						rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 8)
						v := tools.BytesToUint64(rawBytes, item.Endian)
						value = float64(v)
					case "int64":
						rawBytes := tools.BytesPaddingLeft(buffer[item.Start:item.End+1], 8)
						v := tools.BytesToUint64(rawBytes, item.Endian)
						value = float64(int64(v))
					case "float32":
						v := tools.BytesToFloat32(buffer[item.Start:item.End+1], item.Endian)
						value = float64(v)
					case "float64":
						v := tools.BytesToFloat64(buffer[item.Start:item.End+1], item.Endian)
						value = v
					}
					data = append(data, []string{
						item.Addr,
						item.FieldName,
						item.SpecType,
						tools.ByteArrayToHexStr(buffer[item.Start : item.End+1]),
						fmt.Sprintf("%v", tools.FormatFloat(value)),
						fmt.Sprintf("%v", item.MultiByte),
						fmt.Sprintf("%v", item.Start),
						fmt.Sprintf("%v", item.End),
						fmt.Sprintf("%v", item.BoolBits),
					})
					// 变量保存到 redis 队列
					queueData := QueueValue{}
					queueData.SpecType = item.SpecType
					queueData.Addr = item.Addr
					queueData.FieldName = item.FieldName
					queueData.Value = value
					queueData.CreateTime = &now
					messageList = append(messageList, &queueData)
				}
			}

			if len(messageList) > 0 {
				go VariablesMap.PushMessageToClients(messageList)
			}
			/*--- 变量解析结束 ---*/
			if len(data) > 0 {
				table := tablewriter.NewWriter(os.Stdout)
				table.SetHeader([]string{"地址", "变量名", "类型", "原始数据", "解析值", "是否多字节", "START", "END", "BIT"})
				for _, v := range data {
					table.Append(v)
				}
				table.Render()
			}
			frameMatched = true // 匹配到合适帧格式, 终止循环
			if frame.RelatedFrameID != 0 {
				go func(relatedFrameID int) {
					err = RelationFrameTrigger(relatedFrameID)
					if err != nil {
						j_log.LogError([]interface{}{fmt.Sprintf("%s 触发关联帧失败; %s", ppe.Name, err.Error())})
					}
				}(frame.RelatedFrameID)
			}
			break loop1
		}

		if frameMatched == false {
			j_log.LogInfo([]interface{}{"???未知的数据包"})
		}
		clearBuffer(&cacheBuffer, n)
	}
}

// ConnectionPoolHandle TCP连接池管理
func (ppe *PPE) ConnectionPoolHandle(config *TCPIPConfig, ctx context.Context) {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"ConnectionPoolHandle panic", err})
			fmt.Println(string(debug.Stack()))
		}
		j_log.LogInfo([]interface{}{fmt.Sprintf("%s TCP连接池管理协程已停止", ppe.Name)})
	}()
	for {
		select {
		case <-ctx.Done(): // 主进程关闭, 清空连接池, 断开每个client连接, 终止协程
			ppe.TCPOnlineMap.Clear()
			return
		case client := <-ppe.TCPOnlineMap.Register:
			_, ok := ppe.TCPOnlineMap.Get(client.RemoteAddr)
			if !ok {
				if config.MaxClients != 0 && len(ppe.TCPOnlineMap.M) > config.MaxClients {
					j_log.LogWarning([]interface{}{fmt.Sprintf("TCP客户端连接数已满, 抛弃新连接 %s:%s", ppe.Name, client.RemoteAddr)})
					_ = client.Connection.Close()
					continue
				}
				j_log.LogInfo([]interface{}{fmt.Sprintf("新客户端已连接 %s:%s", ppe.Name, client.RemoteAddr)})
				ppe.TCPOnlineMap.Set(client.RemoteAddr, client)
			}
			//client.LastDataTime = tools.GetNow()
		case client := <-ppe.TCPOnlineMap.Unregister:
			_, ok := ppe.TCPOnlineMap.Get(client.RemoteAddr)
			if ok {
				j_log.LogWarning([]interface{}{fmt.Sprintf("用户掉线 %s:%s", ppe.Name, client.RemoteAddr)})
				_ = client.Connection.Close()
				ppe.TCPOnlineMap.Delete(client.RemoteAddr)
			}
		}
	}
}

// TCPTimeoutHandle TCP客户端连接超时处理
func (ppe *PPE) TCPTimeoutHandle(config *TCPIPConfig, ctx context.Context) {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"TCPTimeoutHandle panic", err})
			fmt.Println(string(debug.Stack()))
		}
		j_log.LogInfo([]interface{}{fmt.Sprintf("%s TCP连接池管理协程已停止", ppe.Name)})
	}()
	// 超过一定时间未发送数据, 自动算离线; 1秒一次循环判断, 删除最后一次发送数据的时间大于5秒的client
	if config.TimeoutKick <= 0 {
		j_log.LogInfo([]interface{}{fmt.Sprintf("%s 未设置踢出连接的超时时间, 默认不主动踢出", ppe.Name)})
		return
	}
	for {
		select {
		case <-ctx.Done():
			return
		default:
			now := time.Now()
			ppe.TCPOnlineMap.RLock()
			for _, client := range ppe.TCPOnlineMap.M {
				if now.Sub(tools.StrToTime(client.LastDataTime)) > time.Duration(config.TimeoutKick)*time.Second {
					j_log.LogWarning([]interface{}{fmt.Sprintf("%s:%s TCP客户端长时间未发送数据,已踢掉", ppe.Name, client.RemoteAddr)})
					ppe.TCPOnlineMap.Unregister <- client
				}
			}
			ppe.TCPOnlineMap.RUnlock()
		}
		time.Sleep(1 * time.Second)
	}
}

func clearBuffer(buf *[BYTE_LENGTH]byte, n int) {
	if n >= BYTE_LENGTH {
		n = BYTE_LENGTH
	}
	for i := 0; i < n; i++ {
		buf[i] = 0
	}
}

type TCPOnlineMapStu struct {
	M          map[string]*TCPClient
	Register   chan *TCPClient
	Unregister chan *TCPClient
	sync.RWMutex
}

func (m *TCPOnlineMapStu) Get(key string) (value *TCPClient, ok bool) {
	m.RLock()
	value, ok = m.M[key]
	m.RUnlock()
	return
}

func (m *TCPOnlineMapStu) Set(key string, value *TCPClient) {
	m.Lock()
	m.M[key] = value
	m.Unlock()
}

func (m *TCPOnlineMapStu) Delete(key string) {
	m.Lock()
	delete(m.M, key)
	m.Unlock()
}

func (m *TCPOnlineMapStu) Clear() {
	m.Lock()
	for key, client := range m.M {
		_ = client.Connection.Close()
		delete(m.M, key)
	}
	m.Unlock()
}

func (ppe *PPE) tcpServerFrameSendProcess(ctx context.Context, frame *Frame) {
	defer func() {
		if err := recover(); err != nil {
			j_log.LogError([]interface{}{"tcpServerFrameSendProcess panic", err})
			fmt.Println(string(debug.Stack()))
		}
		j_log.LogInfo([]interface{}{fmt.Sprintf("frame_id=%d, tcpServerFrameSendProcess 协程已关闭", frame.ID)})
	}()
	for {
		select {
		case <-ctx.Done():
			return
		default:
			if frame.SendType == 1 { // 循环发送
				list := make([]*FrameItemParam, 0)
				err := json.Unmarshal([]byte(frame.JsonDetail), &list)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧json解析失败 frame.JsonDetail; %s", frame.ID, err.Error())})
					return
				}
				data, err := CreateFrameRawData(list)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧生成帧原始数据失败; %s", frame.ID, err.Error())})
					return
				}
				totalN, successN, err := ppe.TcpServerMultiWrite(data)
				//j_log.LogInfo([]interface{}{fmt.Sprintf("ppe_id=%d, totalN=%d, successN=%d, lastErr=%v", ppe.ID, totalN, successN, err)})
				if err != nil {
					j_log.LogInfo([]interface{}{fmt.Sprintf("ppe_id=%d, totalN=%d, successN=%d, lastErr=%v\ndata=%v", ppe.ID, totalN, successN, err, data)})
				}
				time.Sleep(time.Duration(frame.ScanRate) * time.Millisecond)
			} else if frame.SendType == 2 { // 事件触发
				match, err := IsTriggerConditionMatched(frame)
				if err != nil {
					j_log.LogError([]interface{}{fmt.Sprintf("[%s] frame_id=%d, IsTriggerConditionMatched error %s", frame.Name, frame.ID, err.Error())})
					return
				}
				if match {
					j_log.LogInfo([]interface{}{fmt.Sprintf("[%s] frame_id=%d, 事件触发帧满足条件", frame.Name, frame.ID)})
					if time.Now().Sub(frame.LastTriggerTime).Seconds() < float64(frame.TriggerInterval) {
						j_log.LogInfo([]interface{}{fmt.Sprintf("[%s] frame_id=%d, 事件触发帧距离下一次触发时间间隔未到, 跳过", frame.Name, frame.ID)})
						time.Sleep(1000 * time.Millisecond)
						continue
					}

					list := make([]*FrameItemParam, 0)
					err = json.Unmarshal([]byte(frame.JsonDetail), &list)
					if err != nil {
						j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧json解析失败 frame.JsonDetail; %s", frame.ID, err.Error())})
						return
					}

					data, err := CreateFrameRawData(list)
					if err != nil {
						j_log.LogError([]interface{}{fmt.Sprintf("frame_id=%d, 发送帧生成帧原始数据失败; %s", frame.ID, err.Error())})
						return
					}

					totalN, successN, err := ppe.TcpServerMultiWrite(data)
					if err != nil {
						j_log.LogInfo([]interface{}{fmt.Sprintf("ppe_id=%d, totalN=%d, successN=%d, lastErr=%v\ndata=%v", ppe.ID, totalN, successN, err, data)})
					}
					time.Sleep(time.Duration(frame.ScanRate) * time.Millisecond)
					frame.LastTriggerTime = time.Now()
				} else {
					j_log.LogInfo([]interface{}{fmt.Sprintf("[%s] frame_id=%d, 事件触发帧不满足条件, 跳过", frame.Name, frame.ID)})
				}
				time.Sleep(1000 * time.Millisecond)
			}
		}
	}
}

func (ppe *PPE) TcpServerMultiWrite(data []byte) (totalNum int, successNum int, err error) {
	if ppe.TCPOnlineMap == nil {
		return
	}
	ppe.TCPOnlineMap.RLock()
	defer ppe.TCPOnlineMap.RUnlock()
	for _, client := range ppe.TCPOnlineMap.M {
		totalNum++
		_, err2 := client.Connection.Write(data)
		if err2 != nil {
			j_log.LogError([]interface{}{fmt.Sprintf("ppe_id=%d  tcp发送失败:%s", ppe.ID, err2.Error())})
			err = err2
			continue
		}
		successNum++
	}
	return
}
